package com.hs.communication.common.fallback.handlers;

import com.hs.common.core.constant.CacheConstants;
import com.hs.common.core.constant.SecurityConstants;
import com.hs.common.core.exception.ServiceException;
import com.hs.common.core.utils.StringUtils;
import com.hs.common.redis.service.RedisService;
import com.hs.communication.common.constant.Code;
import com.hs.communication.common.encrypt.AESEncrypt;
import com.hs.communication.common.encrypt.RSAEncrypt;
import com.hs.communication.common.entity.Charger;
import com.hs.communication.common.entity.ChargerData;
import com.hs.communication.common.feign.RemoteChargerService;
import com.hs.communication.common.message.MessageEntity;
import com.hs.communication.common.producer.IProducer;
import com.hs.communication.common.utils.ByteBuffer;
import com.hs.communication.common.utils.ByteUtil;
import com.hs.communication.common.utils.CrcUtil;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

import static com.hs.communication.common.constant.Code.*;
import static com.hs.communication.common.encrypt.AESEncrypt.aes_iv;
import static com.hs.communication.common.encrypt.AESEncrypt.aes_key;


@Component
public class ChargerHandler {
    public Map<Byte, BiConsumer<String, byte[]>> map = new HashMap<>();

    @Resource
    private RemoteChargerService remoteChargerService;
    @Resource
    private RedisService redisService;


    private static final long OFFLINE_TIMEOUT = 5 * 60 * 1000; // 5分钟无活动认为离线

    @Resource
    private IProducer producer;

    @PostConstruct
    public void init() {
        map.put(LOGIN, this::handle0x01);
        map.put(HEARTBEAT, this::handle0x03);
        map.put(REAL_DATA, this::handle0x11);
    }

    /**
     * 假设一帧协议格式如下
     * * 帧头   设备类型 功能码 帧码   数据长度 数据内容 crc校验
     * * 0x10  1字节   1字节  2字节  2字节    n字节   2字节
     * 设备类型:充电桩-0x00
     * 功能码: 如01表示登录,10表示实时数据
     * 帧码: 同一帧第几次发送,比如实时数据帧每发送一次就+1
     * 数据长度: 数据内容 n字节 的数量n
     * crc校验
     *
     * @param bytes
     */
    public void handle0x01(String connection, byte[] bytes) {
        //sn默认为"charger-nnnnn"共13位数
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        byte[] prefix = buffer.getBytes(3); //一条数据的固定前缀,后面的才是真正的数据
        byte[] frameNumber = buffer.getBytes(2); //帧码
        byte[] dataLength = buffer.getBytes(2); //数据长度
        byte[] sn_bytes = buffer.getBytes(13); //数据长度
        //获取的crc
        byte[] crc_bytes = buffer.getBytes(2);

//        //本地计算的crc
//        byte[] crc_calc = CrcUtil.crc16(sn_bytes);
//
//        if (ByteUtil.bytesToInt(crc_bytes) != ByteUtil.bytesToInt(crc_calc)) {
//            throw new ServiceException("CRC有误");
//        }


        res0x02(sn_bytes, frameNumber);
    }

    public void res0x02(byte[] sn_bytes, byte[] frameNumber) {

        String sn = new String(sn_bytes);
        //获取RSA公钥
        byte[] key = RSAEncrypt.publicKey.getBytes(StandardCharsets.UTF_8);

//        byte[] data = ByteUtil.mergeBytes(sn_bytes, key);

        //将SN+公钥当作数据域回复给设备
        MessageEntity entity = MessageEntity.builder().functionCode(Code.LOGIN_RES).frameNumber(frameNumber).data(key).build();

        byte[] msg = MessageEntity.getBytes(entity);

        //由具体的MQTT或者Socket服务回复02消息
        producer.send(sn, msg);
    }

    //处理03
    public void handle0x03(String connection, byte[] bytes) {

        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        byte[] prefix = buffer.getBytes(3); //一条数据的固定前缀,后面的才是真正的数据
        byte[] frameNumber = buffer.getBytes(2); //帧码
        byte[] dataLength = buffer.getBytes(2); //数据长度
        byte[] data = buffer.getBytes(bytes.length - 7 - 2); //数据长度
        //获取的crc
        byte[] crc_bytes = buffer.getBytes(2);

        //本地计算的crc
        byte[] crc_calc = CrcUtil.crc16(data);

        if (ByteUtil.bytesToInt(crc_bytes) != ByteUtil.bytesToInt(crc_calc)) {
            throw new ServiceException("CRC有误");
        }

        //解密后的明文
        byte[] data_plain = RSAEncrypt.decrypt(data);

        ByteBuffer buffer1 = ByteBuffer.wrap(data_plain);

        byte[] sn_bytes = null;
        //第一次登录的时候会带AES加密的key和value,发送正常心跳包只带sn
        if (data_plain.length > 13) {
            sn_bytes = buffer1.getBytes(13);
            byte[] aes_key = buffer1.getBytes(16);
            byte[] aes_iv = buffer1.getBytes(16);

            String aes_key_str = redisService.getCacheObject(CacheConstants.DEVICE_AES_KEY);
            String aes_iv_str = redisService.getCacheObject(CacheConstants.DEVICE_AES_IV);
            //如果为空,就保存到redis,否则就取出
            if (StringUtils.isEmpty(aes_key_str)) {
                redisService.setCacheObject(CacheConstants.DEVICE_AES_KEY, new String(aes_key));
            }
            if (StringUtils.isEmpty(aes_iv_str)) {
                redisService.setCacheObject(CacheConstants.DEVICE_AES_IV, new String(aes_iv));
            }
        } else if (data_plain.length == 13) {
            sn_bytes = buffer1.getBytes(13);
        }
        AESEncrypt.aes_key = ((String) redisService.getCacheObject(CacheConstants.DEVICE_AES_KEY)).getBytes(StandardCharsets.UTF_8);
        AESEncrypt.aes_iv = ((String) redisService.getCacheObject(CacheConstants.DEVICE_AES_IV)).getBytes(StandardCharsets.UTF_8);


//        System.out.println("AESEncrypt.aes_key=" + new String(aes_key, StandardCharsets.UTF_8));
//        System.out.println("AESEncrypt.aes_iv=" + new String(AESEncrypt.aes_iv, StandardCharsets.UTF_8));

        String sn = new String(sn_bytes);

        System.out.println("更新时的sn=" + sn);

        res0x04(sn, frameNumber);

        //每一次上报都更新redis里面的心跳时间,定时器检查心跳时间有没有超时
        Map<String, LocalDateTime> map = redisService.getCacheMap(CacheConstants.DEVICE_HEARTBEAT_TIME_KEY);
        LocalDateTime now = LocalDateTime.now();
        if (map != null) {
            redisService.setCacheMapValue(CacheConstants.DEVICE_HEARTBEAT_TIME_KEY, sn, now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }


        Charger charger = Charger.builder().sn(sn).status("1").offOnlineTime(now).build();

        //数据库更改为在线状态
        remoteChargerService.updateStatus(charger, SecurityConstants.INNER);
    }

    //回复04
    public void res0x04(String sn, byte[] frameNumber) {
        //回复一个1表示成功
        byte[] data = {0x01};
        MessageEntity entity = MessageEntity.builder().functionCode(HEARTBEAT_RES).frameNumber(frameNumber).data(data).build();

        byte[] msg = MessageEntity.getBytes(entity);

        producer.send(sn, msg);
    }

    /**
     * 获取实时数据
     * 读到的数据域是密文,要先解密
     *
     * @param connection
     * @param bytes
     */
    public void handle0x11(String connection, byte[] bytes) {
        AESEncrypt.aes_key = ((String) redisService.getCacheObject(CacheConstants.DEVICE_AES_KEY)).getBytes(StandardCharsets.UTF_8);
        AESEncrypt.aes_iv = ((String) redisService.getCacheObject(CacheConstants.DEVICE_AES_IV)).getBytes(StandardCharsets.UTF_8);
//        System.out.println("handle0x11 函数");
//        System.out.println("bytes.length=" + bytes.length);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        byte[] prefix = buffer.getBytes(7); //一条数据的固定前缀,后面的才是真正的数据

//        System.out.println("aes_key=" + aes_key);
        //密文数据
        byte[] data_cipher = buffer.getBytes(bytes.length - 7 - 2);
        //获取的crc
        byte[] crc_bytes = buffer.getBytes(2);

        //本地计算的crc
        byte[] crc_calc = CrcUtil.crc16(data_cipher);

//        System.out.println("crc计算");

        if (ByteUtil.bytesToInt(crc_bytes) != ByteUtil.bytesToInt(crc_calc)) {
            throw new ServiceException("CRC有误");
        }

        byte[] data_plain = AESEncrypt.decrypt(aes_key, aes_iv, data_cipher);

//        System.out.println("data_plain=" + data_plain);

        ByteBuffer buffer1 = ByteBuffer.wrap(data_plain);
        if (data_plain != null && data_plain.length > 0) {
            byte[] sn_bytes = buffer1.getBytes(13);
            byte[] report_time_bytes = buffer1.getBytes(6);
            byte[] vol_in_bytes = buffer1.getBytes(2);
            byte[] vol_out_bytes = buffer1.getBytes(2);
            byte[] cur_in_bytes = buffer1.getBytes(2);
            byte[] cur_out_bytes = buffer1.getBytes(2);
            byte[] fre_in_bytes = buffer1.getBytes(2);
            byte[] fre_out_bytes = buffer1.getBytes(2);
            byte[] cha_time_bytes = buffer1.getBytes(2);
            byte[] cha_energy_bytes = buffer1.getBytes(2);


            String sn = ByteUtil.bytesToStr(sn_bytes);
            int year = report_time_bytes[0] + 2000;
            int month = report_time_bytes[1];
            int day = report_time_bytes[2];
            int hour = report_time_bytes[3];
            int minute = report_time_bytes[4];
            int second = report_time_bytes[5];

//            System.out.println("year=" + year);
//            System.out.println("month=" + month);
//            System.out.println("day=" + day);
//            System.out.println("hour=" + hour);
//            System.out.println("minute=" + minute);
//            System.out.println("second=" + second);

            LocalDateTime report_time = LocalDateTime.of(year, month, day, hour, minute, second);

            double vol_in = ByteUtil.bytesToInt(vol_in_bytes) / 10.0;
            double vol_out = ByteUtil.bytesToInt(vol_out_bytes) / 10.0;
            double cur_in = ByteUtil.bytesToInt(cur_in_bytes) / 10.0;
            double cur_out = ByteUtil.bytesToInt(cur_out_bytes) / 10.0;
            double fre_in = ByteUtil.bytesToInt(fre_in_bytes) / 10.0;
            double fre_out = ByteUtil.bytesToInt(fre_out_bytes) / 10.0;
            double cha_time = ByteUtil.bytesToInt(cha_time_bytes) / 10.0;
            double cha_energy = ByteUtil.bytesToInt(cha_energy_bytes) / 10.0;

//            System.out.println("sn=" + sn);
//            System.out.println("report_time=" + report_time);
//            System.out.println("vol_in=" + vol_in);
//            System.out.println("vol_out=" + vol_out);
//            System.out.println("cur_in=" + cur_in);
//            System.out.println("cur_out=" + cur_out);
//            System.out.println("fre_in=" + fre_in);
//            System.out.println("fre_out=" + fre_out);
//            System.out.println("cha_time=" + cha_time);
//            System.out.println("cha_energy=" + cha_energy);

            ChargerData chargerData = ChargerData.builder().sn(sn).connection(connection).reportTime(report_time).voltageInput(vol_in).voltageOutput(vol_out).currentInput(cur_in).currentOutput(cur_out).frequencyInput(fre_in).frequencyOutput(fre_out).chargingTime(cha_time).chargingEnergy(cha_energy).build();

            //最新实时数据放入redis
            redisService.setCacheObject(CacheConstants.LATEST_CHARGER_DATA + sn, chargerData);
            //插入数据库
            remoteChargerService.insert(chargerData, SecurityConstants.INNER);
        }
    }
}
